package logger

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
	"github.com/segmentio/kafka-go"
)

type responseWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w responseWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

type config struct {
	port            string
	withBody        bool
	logthread       string
	logname         string
	envtype         string
	appid           string
	requestidHeader string
	useridHeader    string
	pspanidHeader   string
	cspanidHeader   string
	exceptPrefix    []string
}

// Config 配置
type Config interface {
	apply(*config)
}

type configFunc func(*config)

func (f configFunc) apply(c *config) {
	f(c)
}

// ConfigPort 端口
func ConfigPort(port string) Config {
	return configFunc(func(c *config) {
		c.port = port
	})
}

// ConfigWithBody 记录请求内容
func ConfigWithBody(withBody bool) Config {
	return configFunc(func(c *config) {
		c.withBody = withBody
	})
}

// ConfigLogthread 日志进程
func ConfigLogthread(logthread string) Config {
	return configFunc(func(c *config) {
		c.logthread = logthread
	})
}

// ConfigLogname 日志名
func ConfigLogname(logname string) Config {
	return configFunc(func(c *config) {
		c.logname = logname
	})
}

// ConfigEnvtype 环境
func ConfigEnvtype(envtype string) Config {
	return configFunc(func(c *config) {
		c.envtype = envtype
	})
}

// ConfigAppid 应用
func ConfigAppid(appid string) Config {
	return configFunc(func(c *config) {
		c.appid = appid
	})
}

// ConfigRequestidHeader 调用链header
func ConfigRequestidHeader(requestidHeader string) Config {
	return configFunc(func(c *config) {
		c.requestidHeader = requestidHeader
	})
}

// ConfigUseridHeader 调用链header
func ConfigUseridHeader(useridHeader string) Config {
	return configFunc(func(c *config) {
		c.useridHeader = useridHeader
	})
}

// ConfigPspanidHeader 调用链header
func ConfigPspanidHeader(pspanidHeader string) Config {
	return configFunc(func(c *config) {
		c.pspanidHeader = pspanidHeader
	})
}

// ConfigCspanidHeader 调用链header
func ConfigCspanidHeader(cspanidHeader string) Config {
	return configFunc(func(c *config) {
		c.cspanidHeader = cspanidHeader
	})
}

// ConfigExceptPrefix 排除路径
func ConfigExceptPrefix(exceptPrefix []string) Config {
	return configFunc(func(c *config) {
		c.exceptPrefix = exceptPrefix
	})
}

// SetLogger initializes the logging middleware.
func SetLogger(conn *Connection, configs ...Config) gin.HandlerFunc {
	conf := config{
		port:            "0",
		logname:         "log.CallChainServerLogFilter",
		withBody:        false,
		requestidHeader: "logcontext-requestid",
		useridHeader:    "logcontext-userid",
		pspanidHeader:   "logcontext-pspanid",
		cspanidHeader:   "logcontext-cspanid",
	}
	for _, cfg := range configs {
		cfg.apply(&conf)
	}
	return func(c *gin.Context) {
		request := ""
		response := ""
		var respWriter *responseWriter
		if conf.withBody {
			reqBody, _ := ioutil.ReadAll(c.Request.Body)
			c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
			request = string(reqBody)
		}
		respWriter = &responseWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = respWriter

		start := time.Now()

		c.Next()

		end := time.Now()
		latency := end.Sub(start)

		response = respWriter.body.String()
		go logChain(c, conn, conf, latency, request, response)
	}
}

func logChain(c *gin.Context, conn *Connection, conf config, latency time.Duration, request string, response string) error {

	if conn == nil {
		msg := "Connection does not exist !"
		log.Error().Msg(msg)
		return errors.New(msg)
	}

	path := c.Request.URL.Path
	if conf.exceptPrefix != nil {
		for _, p := range conf.exceptPrefix {
			if strings.HasPrefix(path, p) {
				return nil
			}
		}
	}
	raw := c.Request.URL.RawQuery
	if raw != "" {
		path = path + "?" + raw
	}

	success := "true"
	if len(c.Errors) > 0 {
		success = "false"
	}

	level := "INFO"
	switch {
	case c.Writer.Status() >= http.StatusBadRequest && c.Writer.Status() < http.StatusInternalServerError:
		level = "WARN"
		conf.withBody = true
	case c.Writer.Status() >= http.StatusInternalServerError:
		level = "ERROR"
		conf.withBody = true
	}

	userID := c.GetHeader(conf.useridHeader)
	if userID == "" {
		if u, ok := c.Get("userid"); ok {
			userID = fmt.Sprintf("%v", u)
		}
	}
	requestID := c.GetHeader(conf.requestidHeader)
	if requestID == "" {
		requestID = uuid.New().String()
		c.Header(conf.requestidHeader, requestID)
	}

	// 扩展字段
	extStr := []byte("{}")

	busiData := struct {
		Code    int    `json:"code"`
		Message string `json:"message"`
	}{}
	err := json.Unmarshal([]byte(response), &busiData)
	if err == nil {
		ext := map[string]interface{}{
			"busicode": strconv.Itoa(busiData.Code),
			"busimsg":  busiData.Message,
		}
		extStr, err = json.Marshal(&ext)
	}

	msg := ""
	msgKeys := []string{"logtime", "logthread", "logname", "loglevel", "requestid", "userid", "sessionid", "envtype", "appid", "server", "serverport", "context", "url", "pspanid", "cspanid", "responsecode", "success", "cost", "requestip", "requestsize", "request", "responsesize", "response", "ext"}
	msgMap := map[string]string{
		"logtime":      time.Now().Format("2006-01-02 15:04:05,000"),
		"logthread":    conf.logthread,
		"logname":      conf.logname,
		"loglevel":     level,
		"requestid":    requestID,
		"userid":       userID,
		"sessionid":    "nosessionid",
		"envtype":      conf.envtype,
		"appid":        conf.appid,
		"server":       getLocalIP(),
		"serverport":   conf.port,
		"context":      "",
		"url":          path,
		"pspanid":      c.GetHeader(conf.cspanidHeader),
		"cspanid":      getRandNumStr(9),
		"responsecode": strconv.Itoa(c.Writer.Status()),
		"success":      success,
		"cost":         strconv.Itoa(int(latency.Nanoseconds() / 1e6)),
		"requestip":    c.ClientIP(),
		"requestsize":  strconv.Itoa(int(c.Request.ContentLength)),
		"request":      "",
		"responsesize": strconv.Itoa(int(c.Writer.Size())),
		"response":     "",
		"ext":          string(extStr),
	}
	for _, key := range msgKeys {
		msg += "[" + msgMap[key] + "]"
	}

	if msg != "" {

		err := conn.writer.WriteMessages(
			context.Background(),
			kafka.Message{Value: []byte(msg)},
		)

		if err != nil {
			log.Error().Err(err).Msg("kafka conn error, retry !")
			if err = conn.Reload(); err != nil {
				log.Error().Err(err).Msg("kafka conn retry fail !")
				return err
			}

			if err = conn.writer.WriteMessages(
				context.Background(),
				kafka.Message{Value: []byte(msg)},
			); err != nil {
				log.Error().Err(err).Msg("kafka write fail !")
				return err
			}
		}
	}
	return nil
}
